package controller

import (
	"fmt"
	"sync"
	"time"

	. "gddgame.cc/galaxy/common/dsl/web"
	"gddgame.cc/galaxy/project/neutron/neutron/logic"
	"gddgame.cc/galaxy/utils/def"
)

var (
	mutex = sync.Mutex{}
)

// force: true
// 如果name相同，允许更新
func ValidUnit(context def.Context, name string, target string, force bool) error {
	db := logic.Database()
	var unit logic.Unit
	if u, _ := db.FindUnit(name); u != nil && !force {
		return ValidException(fmt.Sprintf("unit:%s exist", name))
	} else {
		unit = logic.Unit{
			Name:      name,
			CreatedAt: time.Now(),
			StartAt:   time.Now(),
			Target:    target,
		}
	}
	_, _, err := logic.ValidTarget(unit.Target)
	if err != nil {
		return ValidException(err)
	}
	return nil
}

// force: true
// 如果name相同，强制更新
// 如果工作目录已经存在，强制更新
func StartUnit(context def.Context, name string, target string, force bool) error {
	db := logic.Database()
	var unit logic.Unit
	if u, _ := db.FindUnit(name); u != nil {
		if !force {
			return ValidException(fmt.Sprintf("unit:%s exist", name))
		}
		unit = *u
		container := unit.Container()
		context.Infoln("Stop Unit", unit.Name, unit.Target, unit.Pid)
		if err := container.Stop(); err != nil {
			return ValidException(err)
		}
		context.Infoln("Clean Unit", unit.Name, unit.Target, unit.Pid)
		if err := container.Clean(); err != nil {
			return ValidException(err)
		}
		if unit.Status != logic.RollbackStatus && unit.Status != logic.ErrorStatus {
			// 回滚或者错误状态，上一个版本不可用，忽略
			unit.PrevTarget = unit.Target
		}
		unit.Target = target
		unit.RestartNumber += 1
		context.Infoln("Restart Unit", unit.Name, unit.Target)
	} else {
		unit = logic.Unit{
			Name:          name,
			RestartNumber: 0,
			CreatedAt:     time.Now(),
			StartAt:       time.Now(),
			Target:        target,
		}
	}

	u, q, err := logic.ValidTarget(unit.Target)
	context.Infoln("Valid Unit", unit.Target, u, q)
	if err != nil {
		return ValidException(err)
	}
	if err := logic.Analyse(&unit, u, q, force); err != nil {
		return ValidException(err)
	}
	context.Infoln("Analyse Unit", unit.Target, unit.Info)
	container := unit.Container()
	context.Infoln("Unit Container", unit.Target, container)
	mutex.Lock()
	context.Infoln("Container start", unit.Target, container)
	if err := container.Start(); err != nil {
		mutex.Unlock()
		context.Errorln("Container start fail", unit.Target, err)
		return ValidException(err)
	}
	mutex.Unlock()
	context.Infof("Start unit:%s, pid:%d => %v", unit.Name, unit.Pid, unit.Info)
	unit.RecoverNumber = 0
	unit.Status = logic.RunningStatus
	if err := db.AddUnit(&unit); err != nil {
		return SystemException(err)
	}
	return nil
}

// force: true
// 运行中，强制重启
func RestartUnit(context def.Context, name string, force bool) error {
	db := logic.Database()
	unit, err := db.FindUnit(name)
	if err != nil {
		return EmptyException(err)
	}
	mutex.Lock()
	if logic.FindProcess(unit.Pid) {
		if !force {
			mutex.Unlock()
			return nil
		}
		if err := logic.StopProcess(unit.Pid); err != nil {
			mutex.Unlock()
			return ValidException(err.Error())
		}
	}
	container := unit.Container()
	if err := container.Start(); err != nil {
		mutex.Unlock()
		return ValidException(err)
	}
	mutex.Unlock()
	context.Infof("Restart unit:%s, pid:%d => %v", unit.Name, unit.Pid, unit.Info)
	unit.Status = logic.RunningStatus
	unit.RestartNumber += 1
	unit.RecoverNumber = 0
	unit.StartAt = time.Now()
	return db.SaveUnit(unit)
}

func StopUnit(context def.Context, name string) error {
	db := logic.Database()
	unit, err := db.FindUnit(name)
	if unit == nil {
		return EmptyException(err)
	}
	if err := db.DeleteUnit(unit); err != nil {
		return SystemException(err)
	}
	container := unit.Container()
	mutex.Lock()
	_ = container.Stop()
	mutex.Unlock()
	if err := container.Clean(); err != nil {
		return ValidException(err)
	}
	context.Infof("Stop unit:%s", unit.Name)
	return nil
}

func ShowUnit(context def.Context, name string) (map[string]interface{}, error) {
	db := logic.Database()
	unit, err := db.FindUnit(name)
	if unit == nil {
		return nil, EmptyException(err)
	}
	container := unit.Container()
	if unit.Status == logic.ErrorStatus || !container.Find() {
		return map[string]interface{}{
			"name":    unit.Name,
			"pid":     "",
			"start":   unit.StartAt,
			"type":    unit.Type,
			"status":  unit.Status,
			"workDir": logic.GetWorkDir(unit),
			"restart": unit.RestartNumber,
			"cpu":     "0%",
			"mem":     0,
		}, nil
	}
	info := container.Watch()
	result := map[string]interface{}{
		"name":    unit.Name,
		"pid":     unit.Pid,
		"start":   unit.StartAt,
		"type":    unit.Type,
		"status":  unit.Status,
		"workDir": logic.GetWorkDir(unit),
		"restart": unit.RestartNumber,
		"cpu":     info["cpu"],
		"mem":     info["mem"],
	}
	return result, nil
}

func ShowAllUnit(context def.Context) ([]map[string]interface{}, error) {
	db := logic.Database()
	units, err := db.LoadAllUnit()
	if err != nil {
		return nil, err
	}
	result := make([]map[string]interface{}, len(units))
	for index, unit := range units {
		info, _ := ShowUnit(context, unit.Name)
		result[index] = info
	}
	return result, nil
}

func RecoverUnit(context def.Context, name string) error {
	db := logic.Database()
	unit, err := db.FindUnit(name)
	if unit == nil {
		return EmptyException(err)
	}

	mutex.Lock()
	if logic.FindProcess(unit.Pid) {
		mutex.Unlock()
		return nil
	}
	mutex.Unlock()

	if unit.RecoverNumber == 0 && unit.PrevTarget != "" && unit.Status != logic.RollbackStatus {
		// 回滚
		u, q, err := logic.ValidTarget(unit.PrevTarget)
		if err != nil {
			return ValidException(err)
		}
		if err := logic.Analyse(unit, u, q, true); err == nil {
			// 回滚验证成功
			unit.Status = logic.RollbackStatus
			context.Infof("Recover unit: %s Rollback %s -> %s", unit.Name, unit.Target, unit.PrevTarget)
		} else {
			return ValidException(err)
		}

	} else {
		if !logic.AllowRecover(unit) {
			unit.Status = logic.ErrorStatus
			context.Infof("Recover unit: %s max times, ErrorStatus", unit.Name)
			// todo 发送通知
			return db.SaveUnit(unit)
		}
	}

	mutex.Lock()
	container := unit.Container()
	if err := container.Start(); err != nil {
		mutex.Unlock()
		return ValidException(err)
	}
	mutex.Unlock()
	context.Infof("Recover unit:%s, pid:%d => %v", unit.Name, unit.Pid, unit.Info)
	unit.RestartNumber += 1
	unit.RecoverNumber += 1
	unit.StartAt = time.Now()
	return db.SaveUnit(unit)
}
